# -*- coding: utf-8 -*-
from __future__ import print_function
"""
Event handling and helper classes.

:Variables:
    HANDLED : Constant
        Constant to return when a event handler wants to prevent
        further processing.
    UNHANDLED : Constant
        Optional return value when a event does not want to interrupt
        event processing.
    NEW_FIRST : Constant
        Specifies that the listener added later will be called in first place.
    NEW_LAST : Constant
        Specifies that the listener added later will be called after the
        the other registered listeners.
    USE_EVENT_TYPE_ORDER : Constant
        Specifies to use the call order of the registered event type.

"""

__author__ = "dr0iddr0id {at} gmail [dot] com (C) 2010"

import logging
_LOGGER = logging.getLogger('pyknic.events')

# -------------------------------------------------------------------------------
if __debug__:
    _LOGGER.debug('%s loading ... \n' % (__name__))
    import time
    _START_TIME = time.time()
# -------------------------------------------------------------------------------

import types
from weakref import ref # pragma: no cover

# -------------------------------------------------------------------------------
HANDLED = True
UNHANDLED = False
NEW_FIRST = 0
NEW_LAST = -1
USE_EVENT_TYPE_ORDER = None

class Signal(object):
    """
    A signal object. It is for event dispatching. It saves a list of
    observers and when fired, it calls the observers. The handler method
    has to accept the exact same number of arguments that are given to
    the fire method. Or it should make use of the args and kwargs arguments.
    The return type of a handler is important. When returning True or any value
    that evaluates to True in a if statement, the signal stops calling further
    observers. Returning False or None (as default behavior of methods without a
    return), it will call further handler methods.

    The order in which the handler methods will be called can be defined
    when the signal is instanciated. The sort order can only have one of
    these values:

     * Signal.`NEW_FIRST`
     * Signal.`NEW_LAST`


    Default is NEW_FIRST. This means that handlers added later are called before
    the older ones::

       s = Signal()
       s.add(handler1)
       s.add(handler2)
       s.fire(sender) # call order: handler2, handler1

    This is important and useful, when imitating a push behavior. If handler2
    returns a `HANDLED` then handler1 wont get be called.

    Using the `NEW_LAST` order for event handler leads to this::

       s = Signal()
       s.add(handler1)
       s.add(handler2)
       s.fire(sender) # call order: handler1, handler2

    This is useful for a draw event. A convinient way to draw is in a back to
    front manner. Adding first the background and then the other things on top
    will draw the things in the correct order.

    This Signal implementation is iteration safe. It is possible to remove
    the handler from within the handler without getting in trouble.

    :Warning:
        Any class that has a method added will be kept alive by this reference.
        To allow the class to die it has to remove the handler. See `WeakSignal`
        for a implementation using weak references.

    :Ivariables:
        name : string
            Either the string that was given or None.
        enabled : bool
            If this Signal fires or not.
        cur_event_list : list
            current list of methods to call during firing. Only used if chaining
            signals.

    :Cvariables:
        NEW_FIRST : int
            Used to define the call order. New handlers are added in front of
            the others and therefore called before the others.
        NEW_LAST : int
            Used to define the call order. New handlers are appended
            and therefore called after the others.

    :Note:
        If you get trouble with the recursion limit then try to avoid the
        shortcuts::

            sig += sig2 # use sig += sig2.fire instead
            sig -= sig2 # use sig -= sig2.fire instead

        This should double the available recursion depth due to less function
        calls done.

    """

    _ADD, _RMV, _CLR = list(range(3))


    def __init__(self, name=None, sort_order = NEW_FIRST):
        """
        Constructor.

        :Parameters:
            name : string
                Optional. A name to identify the signal easier when debugging.
            sort_order : Constant
                Either NEW_FIRST or NEW_LAST. Defines the order in
                which the handler are called. Defaults to NEW_FIRST.

        """
        # -- public -- #
        if name:
            self.name = name
        else:
            self.name = hex(id(self))
        self.enabled = True
        self.fire = self._fire_normal
        # -- protected -- #
        # observers
        self._observers = []
        # sortorder
        self._sort_oder = sort_order
        # commands
        self._commands = []

    def add(self, obs):
        """
        Adds a handler to the signal.

        :Note:
            Shortcut::

                sig += obs

        :Parameters:
            obs : callable
                A handler to add, has to be callable.
        """
        self.fire = self._fire_changed
        self._commands.append((self._ADD, obs))
        return self

    def remove(self, obs):
        """
        Removes a handler.

        :Note:
            Shortcut::

                sig -= obs

        :Parameters:
            obs : callable
                The handler to be removed.
        """
        self.fire = self._fire_changed
        self._commands.append((self._RMV, obs))
        return self

    def _fire_normal(self, *args, **kwargs):
        if self.enabled:
            for observer in self._observers:
                if observer(*args, **kwargs):
                    return HANDLED
        return UNHANDLED
        
    def _fire_changed(self, *args, **kwargs):
        """
        The fire method used if changes have occured, calls _fire_normal.
        """
        self._sync()
        return self.fire(*args, **kwargs)
            
    def fire(self, *args, **kwargs):
        """
        Fires the signal with any arguments.

        :Note:
            Shortcut::

                sig(sender, *args, **kwargs)

            Be aware of recursion, see class introduction.

        :Parameters:
            sender : object
                The sender of this event.
            args : args
                Arguments list.
            kwargs : kwargs
                Named arguments, a dict.

        :rtype: True when a handler returns `HANDLED` , else False
        """
        # its here for documentation, actual implementation see _fire_changed and _fire_normal
        pass
        
    def clear(self):
        """
        Removes all handlers from the signal.
        """
        self._commands.append((self._CLR, []))

    def _sync(self):
        """
        Only used internaly. This method is for synchronizing
        the added or removed observers.
        """
        # self._changed = False
        while self._commands:
            cmd, obs = self._commands.pop(0)
            if cmd == self._ADD and obs not in self._observers:
                if self._sort_oder == NEW_FIRST:
                    self._observers.insert(0, obs) # start
                else:
                    self._observers.append(obs) # end
            elif cmd == self._RMV:
                if obs in self._observers:
                    self._observers.remove(obs)
            elif cmd == self._CLR:
                self._observers = []
                self._commands = []
        self.fire = self._fire_normal

    def __len__(self):
        """
        Returns the number of handlers. Schould not be called from within a
        handler of this event.
        """
        self._sync()
        return len(self._observers)

    def __str__(self):
        return '<%s(\'%s\') %s at %s>' % (self.__class__.__name__,
                                       self.name,
                                       self._observers,
                                       hex(id(self)))

    # convinients (but slow)
    __iadd__ = add
    __isub__ = remove

# -------------------------------------------------------------------------------
# could this be replaced by weakref.proxy ?????
class WeakMethodRef(object): # pylint: disable=R0903
                             
    """
    It is a wrapper class to hold a weak reference to a bound method.
    It acts exactly the same way as the weakref.ref class.
    """

    def __init__(self, method):
        """
        Save the method we want to hold a reference.

        :Parameters:
            method : callable
                A class method which should referenced weakly.
        """
        super(WeakMethodRef, self).__init__()
        self.instance = ref(method.__self__)
        self.function = method.__func__

    def __call__(self):
        """
        If you call the object return either the method/function object or
        None if it has died. Arguments are ignored.
        """
        inst = self.instance()
        if inst:
            return types.MethodType(self.function, inst)
        return None

    def __hash__(self):
        """
        Hashing method.
        :return: object.__hash__(self)
        """
        return object.__hash__(self)

    def __eq__(self, other):
        """
        equal operator, two instances are equal if for the bound method the
        instances and the method objects are equal. Returns True or False.
        """
        return (self.instance() == other.instance() and \
                                            self.function == other.function)

    def __ne__(self, other):
        """ returns not self.__eq__(other)"""
        return not self.__eq__(other)

    def __str__(self):
        inst = self.instance()
        if inst:
            return '<%s->%s.%s(...) >' % (self.__class__.__name__,
                                          inst.__class__.__name__,
                                          self.function.__name__)
        else:
            return '<%s->\'dead\'>' % (self.__class__.__name__)
# -------------------------------------------------------------------------------

class WeakSignal(Signal):
    """
    A signal implementation that uses weak references. It has some overhead and
    is therefor slower than `Signal`. It has the advantage that it does not
    hold any object alive only because it is added. It uses the `WeakMethodRef`
    to store references to methods of a class (because the class methods are
    actually thin wrapper that dies using normal ref). Funtions are saved using
    the normal ref.

    Behavior is the same as in `Signal`.

    :Warning:
        Might have some preformance impact.
    """
    def _fire_normal(self, *args, **kwargs):
        """
        The fire method used if no changes have occured.
        """
        if self.enabled:
            for obs in self._observers:
                func = obs()
                if func:
                    if func(*args, **kwargs):
                        return HANDLED
        return UNHANDLED

    def _sync(self):
        """
        Only used internaly. This method is for synchronizing
        the added or removed observers.
        """
        # self._changed = False
        while self._commands:
            cmd, obs = self._commands.pop(0)
            if cmd == self._ADD:
                if hasattr(obs, '__self__') and obs.__self__:
                    # bound method
                    obs = WeakMethodRef(obs)
                else:
                    # unbound function
                    obs = ref(obs)
                if obs not in self._observers:
                    self._observers.insert(self._sort_oder, obs)
            elif cmd == self._RMV:
                if not isinstance(obs, WeakMethodRef):
                    obs = WeakMethodRef(obs)
                for obs2 in self._observers:
                    if obs == obs2:
                        self._observers.remove(obs2)
                        break
            elif cmd == self._CLR:
                self._observers = []
                self._commands = []
        self.fire = self._fire_normal

# -------------------------------------------------------------------------------

class SignalProvider(object): # pylint: disable=R0903
    """
    Signal provider provides `Signal` on request.

    It automatically makes a `Signal` instance and save it
    under the requested name. A name is requested in two ways::

        sig_provider = SignalProvider()
        # just as attribut access
        signal1 = sig_provider.name_to_be_requested
        # or as dict lookup
        signal2 = sig_provider['name_to_be_requested']
        signal1 == signal2 # True

    :Ivariables:
        enabled : bool
            Set/get enabled. The enabled value is set for all signals.
            Individual Signal can be enabled/disabled individually, but will
            be overriden by this value.

    :Warning:
        Watch out for typo's (misspelled name wont give an error just a new
        `Signal` instance.

    :Note:
        If this proves to be to error prone a registration interface
        will be added in future.

    :Parameters:
        signal_type : Signal
            The class that should be instanciated for the signals.
            Either `Signal` or `WeakSignal`.

    """

    def __init__(self, signal_type=Signal):
        self._signal_class = signal_type
        self._enabled = True
        self._signals = defaultdict(lambda: None)

    def set_enabled(self, value):
        """Set the enabled value."""
        value = bool(value)
        self._enabled = value
        for sig in list(self._signals.values()):
            sig.enabled = value

    def get_enabled(self):
        """Returns the enabled flag"""
        return self._enabled

    enabled = property(get_enabled, set_enabled, """get/set enabled""")

    def __getattr__(self, name):
        # this isnt called if it is already set
        # make new instance of the provided (signal) class
        sig = self._signal_class(name)
        sig.enabled = self.enabled
        # add it so next time it is found by __getitem__
        setattr(self, name, sig)
        self._signals[name] = sig
        if __debug__:
            _LOGGER.debug('created Signal: %s' % (sig))
        return sig

    def __getitem__(self, name):
        sig = self._signals[name]
        if sig is None:
            return self.__getattr__(name)
        return sig

    def __str__(self):
        return '<SignalProvider%s at %s >' % (str(self.__dict__),
                                              hex(id(self)))


# -------------------------------------------------------------------------------

import collections
from collections import defaultdict
import warnings
import inspect

class EventTypeUnkownException(Exception):
    """
    Exception that is thrown if a event type is unknown.
    """
    pass
    
class EventTypeAlreadyRegisteredError(Exception):
    """
    Exception that is thrown if an event type is already registered.
    """
    pass

class EventDispatcher(object):
    """
    The EventDispatcher does dispatch events to the listeners.

    """

    _event_types = {} # {event_type: sort_order}

    # thi attribute are created per instance only if needed
    _listeners = None #collections.defaultdict(list) # {event_type:[listeners]}

    @classmethod
    def register_event_type(cls, event_type, sort_order=NEW_FIRST):
        """
        Registers an event type. All instances of the EventDispatcher know
        about all event types.
        Raises a 'EventTypeAlreadyRegisteredError' if a event type is registered twice.

        :Parameters:
            event_type : hashable
                normally a string, any hashable object.
            sort_order : (default) NEW_FIRST or NEW_LAST
                defines in which order new listener for this type are inserted.
        """
        if event_type in cls._event_types:
            raise EventTypeAlreadyRegisteredError(str(event_type) + " is already registered")
        cls._event_types[event_type] = sort_order

    @classmethod
    def is_event_type_valid(cls, event_type):
        """
        Check if the event type is already registered.

        :Parameters:
            event_type : hashable
                event type to check
        :rtype: bool
        """
        return (event_type in cls._event_types)

    @property # getter, read-only
    def event_types(self):
        """
        Read only porperty.

        :returns: Returns a list of the registered event types.
        :rtype: list
        """
        return list(self._event_types.keys())

    # TODO: add kwargs
    # TODO: add concept of a frame (otherwise does push make sense?)
    def push_listeners(self, *objects):
        """
        Adds zero or more listeners. If it is a callable, then the callable`s
        '__name__' attribute is used to add it to the listeners. Any other
        object may be specified, in which case it will be searched for
        callables with event names. The listeners will be inserted using
        the sort order that the registered event type has.

        usage::

            def func1(): ...
            def func2(): ...

            class MyListenerClass(): ...

            # using functions
            dispatcher = EventDispatcher()
            dispatcher.push_listeners(func1, func2)

            # using objects
            my_listener = MyListenerClass()
            dispatcher.push_listeners(my_listener)

            # functions and objects can be mixed
            dispatcher.push_listeners(my_listener, func1)



        :Note:

            Dont push a list of listeners. If a list already exist use *
            magic::

                list_of_listeners = [...]

                # without the * the list would be pushed, but the list is no
                # listener and nothing would happen
                dispatcher.push_listeners(*list_of_listeners)


        :Parameters:
            objects : functions, objects
                The objects or functions to add the listeners
        """
        for event_type, listener in self._extract_listeners(objects):
            self.add_listener(event_type, listener, \
                                        self._event_types[event_type])

    def pop_listeners(self, *objects):
        """
        Removes the specified listeners.

        See :func:`push_listeners` for parameter details.
        """
        for event_type_and_listener in self._extract_listeners(objects):
            self.remove_listener(event_type_and_listener[1])

    def _extract_listeners(self, objects):
        """
        Extracts the callables with event type names.

        :return: list of tuples containing (event_type, listener)
        :rtype: tuples
        """
        for obj in objects:
            if __debug__:
                zero_listeners = True
            if inspect.isroutine(obj):
                # single magincally named function
                if obj.__name__ in self._event_types:
                    if __debug__:
                        zero_listeners = False
                    yield obj.__name__, obj
                else:
                    msg = "Event type '" + obj.__name__ + "' is unkown, the \
name of routines pushed needs to be a known event type"
                    raise EventTypeUnkownException(msg)
            else:
                for name in dir(obj):
                    # magically named methods of a single instance
                    if name in self._event_types:
                        if __debug__:
                            zero_listeners = False
                        yield name, getattr(obj, name)
            if __debug__:
                if zero_listeners:
                    msg = "WARNING: missing an event tpye? or obj is an \
iterable (might be wrong)? no listeners have been added for '" + str(obj) + "'"
                    warnings.warn(msg)

    def add_listener(self, event_type, listener, sort_order=USE_EVENT_TYPE_ORDER):
        """
        Adds a listener for the specified event type.

        :Parameters:
            event_type : hashable
                The event type to add this listener
            listener : callable
                the listener method
            sort_order : (default) NEW_FIRST, NEW_LAST, USE_EVENT_TYPE_ORDER
                Specifies where the listener is inserted. When using
                USE_EVENT_TYPE_ORDER raises an EventTypeUnkownException
                if the event type is not already registered.

        """
        if self._listeners is None:
            self._create_listeners()

        assert id(self._listeners) != id(EventDispatcher._listeners)

        # this is not needed for defaultdict, but maybe for weakkeydict
        # if not self._listeners.has_key(event_type):
            # self._listeners[event_type] = []

        if __debug__:
            if listener in self._listeners[event_type]:
                msg = "WARNING: adding listener '" + listener.__name__ + \
                        "' twice to event type '" + \
                        str(event_type) + "', ignore this if intentional"
                warnings.warn(msg)

        if sort_order == USE_EVENT_TYPE_ORDER:
            if not self.is_event_type_valid(event_type):
                msg = "Event type '" + str(event_type) + \
"' needs to be registered first if using sort_order=None in add_listener"
                raise EventTypeUnkownException(msg)
            else:
                sort_order = self._event_types[event_type]

        if sort_order == NEW_FIRST:
            self._listeners[event_type].insert(0, listener)
        else:
            self._listeners[event_type].append(listener)

    # TODO: all flag?
    def remove_listener(self, listener):
        """
        Removes the first encounter of the listener for each event type.

        :Returns: number of removed entries.
        :rtype: int
        """
        count = 0
        for listeners in list(self._listeners.values()):
            try:
                listeners.remove(listener)
                count += 1
            except ValueError:
                pass
        return count

    def fire(self, event_type, *args):
        """
        Calls the listeners methods immediatly.

        :Raises: An AssertionError is raised if the event_type has not
            been registered previously.

        :Note:
            Be aware that if code that generates events A and B sequencially,
            and a listener catches event A and generates event C that is also
            fired, this will procudes the sequence A, C, B.

        :Parameters:
            evenet_type : hashable object
                a registered event_type
            args : any
                the event arguments

        :Returns:
            If the event has been handled or not.
        :rtype:
            HANDLED, UNHANDLED

        """

        assert event_type in self._event_types

        if self._listeners is not None:
            for listener in list(self._listeners[event_type]):
                if listener(*args):
                    return HANDLED
        return UNHANDLED

    def _create_listeners(self):
        """
        Creates the internal listeners data structure.
        Maybe its useful to replace the current data structure
        with another one in the future (maybe WeakKeyDictionary).
        """
        # {event_type:[listeners]}
        self._listeners = collections.defaultdict(list)
        # self._listeners = {}

# -------------------------------------------------------------------------------

class EventQueueDispatcher(EventDispatcher):
    """
    An event dispatcher with an event queue capability.
    :Parameters:
        event_callback : callable
            defaults to None, is a callback function that returns a iterable of tuples (event_type, args).
            The event_type is one of the registered event types and args is a list of arguments or an empty list if
            there are no arguments.
            The idea is that if you have a framework that already has its event queue (like pygame), you can integrate it 
            through such a callback.
    """

    avg_events = 100 # start with an average of 100 events per tick
    
    def __init__(self, event_callback=None):
        self.event_callback = event_callback
        self._queue = collections.deque()
        self._listeners = None

    def enqueue_event(self, event_type, *args):
        """
        Puts a new event on the event queue.

        :Note:
            If a code snippet produces the events A and B sequencially,
            and if a listener catches event A and generates an event C
            by putting it on the event queue, the events are processed
            in following order A, B, C.

        :Warning: 
            Mixing :func:`fire` and :func:`enqueue_event` can produce 
            unpredictable order of event processing.

        :Parameters:
            event_type : hashable
                the event type to queue
            args : any
                the event arguments
                
        """
        assert event_type in self._event_types

        if self._listeners is None:
            self._create_listeners()

        if self.event_callback:
            self._engqueue_callback_events()

        self._queue.append((event_type, args))

    def _engqueue_callback_events(self):
        for event_type_and_args_tuple in self.event_callback():
            self._queue.append(event_type_and_args_tuple)

    # def tick(self, growth = 0.1):
        # """
        # Processes up to the average events per tick.

        # :Parameters:
            # growth : float
                # The maximal change rate of the average number of events
                # processed in a frame. Its main purpose is to average the
                # number of events processed per frame. Example: if the average
                # is 0 events and then 100 events are put in the queue, next
                # frame it will process 10 events, the next frame 19 events,
                # then 24 events and so on.

        # :returns: True if it could process all events in the queue,
            # False otherwise
        # :rtype: bool
        # """
        # # ensure framework events are appended too
        # if self.event_callback:
            # self._engqueue_callback_events()
            
        # # max_num = len(self._queue)
        # queue_len = len(self._queue)
        # if queue_len < self.avg_events:
            # max_num = queue_len
        # else:
            # max_num = self.avg_events
        # count = 0
        # while count < max_num:
            # count += 1
            # event_type, args = self._queue.popleft()

            # assert event_type in self._event_types

            # for listener in list(self._listeners[event_type]):
                # if listener(*args):
                    # break # HANDLED
        # # weighted average, maybe the weights need some adjustment
        # # self.avg_events = ((1.0-growth) * self.avg_events+growth * queue_len)
        # self.avg_events = self.avg_events + growth*(queue_len - self.avg_events)
        # if __debug__:
            # if count != queue_len:
                # _LOGGER.debug("%s %s could not process all events, still \
# %i in queue!" % (self.__class__.__name__, id(self), queue_len - count))
        # return count == queue_len
        
    def update_all(self):
        """
        Processes all events of the event queue. If there are many, this might block
        until all event have been processed.
        
        :warnin: if a handler produces events, this might turn in a endless loop! (unless using enqueue_event)
        
        .. todo:: unittests
        """
        # ensure framework events are appended too
        if self.event_callback:
            self._engqueue_callback_events()
        
        # prevent endless loop of processing
        # this way other events could slip in between the endless processing
        _process_queue = self._queue
        self._queue = collections.deque()
        
        while len(_process_queue) > 0:
            event_type, args = _process_queue.popleft()

            assert event_type in self._event_types

            for listener in list(self._listeners[event_type]):
                if listener(*args):
                    break # HANDLED

    def update_timed(self, get_time, max_delay):
        """
        Processes the events from its queue until the queue is either empty or
        the max_delay time is up.
        
        :Parameters:
            get_time : callable
                Should return the current time. Unit should correspond to max_delay units::
                    get_time() #-> milliseconds
            max_delay : number
                The max duration it can be processing events. Units should correspond to get_time units::
                    max_delay = 100 # milliseconds
        
        .. todo:: unittests
        """
        # ensure framework events are appended too
        if self.event_callback:
            self._engqueue_callback_events()

        _process_queue = self._queue
            
        delay = 0
        start = get_time()
        while len(_process_queue) > 0:
            event_type, args = _process_queue.popleft()

            assert event_type in self._event_types

            for listener in list(self._listeners[event_type]):
                if listener(*args):
                    break # HANDLED
            delay = get_time() - start
            if delay >= max_delay:
                if __debug__:
                    _LOGGER.debug("update_timed used max_time '%s', left events in queue: '%s'" % (max_delay, len(_process_queue)))
                break

    def __len__(self):
        """
        :returns: the number of queued events.
        :rtype: int
        """
        return len(self._queue)

# -------------------------------------------------------------------------------

if __debug__:
    _DELTA = time.time() - _START_TIME
    _LOGGER.debug('%s loaded: %fs \n' % (__name__, _DELTA))
